home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 10900 / 10900.xpi / modules / Preferences.js < prev    next >
Text File  |  2009-11-25  |  16KB  |  447 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Preferences.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Myk Melez <myk@mozilla.org>
  22.  *   Daniel Aquino <mr.danielaquino@gmail.com>
  23.  *
  24.  * Alternatively, the contents of this file may be used under the terms of
  25.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  26.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27.  * in which case the provisions of the GPL or the LGPL are applicable instead
  28.  * of those above. If you wish to allow use of your version of this file only
  29.  * under the terms of either the GPL or the LGPL, and not to allow others to
  30.  * use your version of this file under the terms of the MPL, indicate your
  31.  * decision by deleting the provisions above and replace them with the notice
  32.  * and other provisions required by the GPL or the LGPL. If you do not delete
  33.  * the provisions above, a recipient may use your version of this file under
  34.  * the terms of any one of the MPL, the GPL or the LGPL.
  35.  *
  36.  * ***** END LICENSE BLOCK ***** */
  37.  
  38. let EXPORTED_SYMBOLS = ["Preferences"];
  39.  
  40. const Cc = Components.classes;
  41. const Ci = Components.interfaces;
  42. const Cr = Components.results;
  43. const Cu = Components.utils;
  44.  
  45. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  46.  
  47. // The minimum and maximum integers that can be set as preferences.
  48. // The range of valid values is narrower than the range of valid JS values
  49. // because the native preferences code treats integers as NSPR PRInt32s,
  50. // which are 32-bit signed integers on all platforms.
  51. const MAX_INT = Math.pow(2, 31) - 1;
  52. const MIN_INT = -MAX_INT;
  53.  
  54. function Preferences(prefBranch) {
  55.   if (prefBranch)
  56.     this._prefBranch = prefBranch;
  57. }
  58.  
  59. Preferences.prototype = {
  60.   /**
  61.    * Get the value of a pref, if any; otherwise return the default value.
  62.    *
  63.    * @param   prefName  {String|Array}
  64.    *          the pref to get, or an array of prefs to get
  65.    *
  66.    * @param   defaultValue
  67.    *          the default value, if any, for prefs that don't have one
  68.    *
  69.    * @returns the value of the pref, if any; otherwise the default value
  70.    */
  71.   get: function(prefName, defaultValue) {
  72.     if (isArray(prefName))
  73.       return prefName.map(function(v) this.get(v, defaultValue), this);
  74.  
  75.     switch (this._prefSvc.getPrefType(prefName)) {
  76.       case Ci.nsIPrefBranch.PREF_STRING:
  77.         return this._prefSvc.getComplexValue(prefName, Ci.nsISupportsString).data;
  78.  
  79.       case Ci.nsIPrefBranch.PREF_INT:
  80.         return this._prefSvc.getIntPref(prefName);
  81.  
  82.       case Ci.nsIPrefBranch.PREF_BOOL:
  83.         return this._prefSvc.getBoolPref(prefName);
  84.  
  85.       case Ci.nsIPrefBranch.PREF_INVALID:
  86.         return defaultValue;
  87.  
  88.       default:
  89.         // This should never happen.
  90.         throw "Error getting pref " + prefName + "; its value's type is " +
  91.               this._prefSvc.getPrefType(prefName) + ", which I don't know " +
  92.               "how to handle.";
  93.     }
  94.   },
  95.  
  96.   /**
  97.    * Set a preference to a value.
  98.    *
  99.    * You can set multiple prefs by passing an object as the only parameter.
  100.    * In that case, this method will treat the properties of the object
  101.    * as preferences to set, where each property name is the name of a pref
  102.    * and its corresponding property value is the value of the pref.
  103.    *
  104.    * @param   prefName  {String|Object}
  105.    *          the name of the pref to set; or an object containing a set
  106.    *          of prefs to set
  107.    *
  108.    * @param   prefValue {String|Number|Boolean}
  109.    *          the value to which to set the pref
  110.    *
  111.    * Note: Preferences cannot store non-integer numbers or numbers outside
  112.    * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
  113.    * store it as a string by calling toString() on the number before passing
  114.    * it to this method, i.e.:
  115.    *   Preferences.set("pi", 3.14159.toString())
  116.    *   Preferences.set("big", Math.pow(2, 31).toString()).
  117.    */
  118.   set: function(prefName, prefValue) {
  119.     if (isObject(prefName)) {
  120.       for (let [name, value] in Iterator(prefName))
  121.         this.set(name, value);
  122.       return;
  123.     }
  124.  
  125.     let prefType;
  126.     if (typeof prefValue != "undefined" && prefValue != null)
  127.       prefType = prefValue.constructor.name;
  128.  
  129.     switch (prefType) {
  130.       case "String":
  131.         {
  132.           let string = Cc["@mozilla.org/supports-string;1"].
  133.                        createInstance(Ci.nsISupportsString);
  134.           string.data = prefValue;
  135.           this._prefSvc.setComplexValue(prefName, Ci.nsISupportsString, string);
  136.         }
  137.         break;
  138.  
  139.       case "Number":
  140.         // We throw if the number is outside the range, since the result
  141.         // will never be what the consumer wanted to store, but we only warn
  142.         // if the number is non-integer, since the consumer might not mind
  143.         // the loss of precision.
  144.         if (prefValue > MAX_INT || prefValue < MIN_INT)
  145.           throw("you cannot set the " + prefName + " pref to the number " +
  146.                 prefValue + ", as number pref values must be in the signed " +
  147.                 "32-bit integer range -(2^31-1) to 2^31-1.  To store numbers " +
  148.                 "outside that range, store them as strings.");
  149.         this._prefSvc.setIntPref(prefName, prefValue);
  150.         if (prefValue % 1 != 0)
  151.           Cu.reportError("Warning: setting the " + prefName + " pref to the " +
  152.                          "non-integer number " + prefValue + " converted it " +
  153.                          "to the integer number " + this.get(prefName) +
  154.                          "; to retain fractional precision, store non-integer " +
  155.                          "numbers as strings.");
  156.         break;
  157.  
  158.       case "Boolean":
  159.         this._prefSvc.setBoolPref(prefName, prefValue);
  160.         break;
  161.  
  162.       default:
  163.         throw "can't set pref " + prefName + " to value '" + prefValue +
  164.               "'; it isn't a String, Number, or Boolean";
  165.     }
  166.   },
  167.  
  168.   /**
  169.    * Whether or not the given pref has a value.  This is different from isSet
  170.    * because it returns true whether the value of the pref is a default value
  171.    * or a user-set value, while isSet only returns true if the value
  172.    * is a user-set value.
  173.    *
  174.    * @param   prefName  {String|Array}
  175.    *          the pref to check, or an array of prefs to check
  176.    *
  177.    * @returns {Boolean|Array}
  178.    *          whether or not the pref has a value; or, if the caller provided
  179.    *          an array of pref names, an array of booleans indicating whether
  180.    *          or not the prefs have values
  181.    */
  182.   has: function(prefName) {
  183.     if (isArray(prefName))
  184.       return prefName.map(this.has, this);
  185.  
  186.     return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
  187.   },
  188.  
  189.   /**
  190.    * Whether or not the given pref has a user-set value.  This is different
  191.    * from |has| because it returns true only if the value of the pref is a user-
  192.    * set value, while |has| returns true if the value of the pref is a default
  193.    * value or a user-set value.
  194.    *
  195.    * @param   prefName  {String|Array}
  196.    *          the pref to check, or an array of prefs to check
  197.    *
  198.    * @returns {Boolean|Array}
  199.    *          whether or not the pref has a user-set value; or, if the caller
  200.    *          provided an array of pref names, an array of booleans indicating
  201.    *          whether or not the prefs have user-set values
  202.    */
  203.   isSet: function(prefName) {
  204.     if (isArray(prefName))
  205.       return prefName.map(this.isSet, this);
  206.  
  207.     return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName));
  208.   },
  209.  
  210.   /**
  211.    * Whether or not the given pref has a user-set value. Use isSet instead,
  212.    * which is equivalent.
  213.    * @deprecated
  214.    */
  215.   modified: function(prefName) { return this.isSet(prefName) },
  216.  
  217.   reset: function(prefName) {
  218.     if (isArray(prefName)) {
  219.       prefName.map(function(v) this.reset(v), this);
  220.       return;
  221.     }
  222.  
  223.     try {
  224.       this._prefSvc.clearUserPref(prefName);
  225.     }
  226.     catch(ex) {
  227.       // The pref service throws NS_ERROR_UNEXPECTED when the caller tries
  228.       // to reset a pref that doesn't exist or is already set to its default
  229.       // value.  This interface fails silently in those cases, so callers
  230.       // can unconditionally reset a pref without having to check if it needs
  231.       // resetting first or trap exceptions after the fact.  It passes through
  232.       // other exceptions, however, so callers know about them, since we don't
  233.       // know what other exceptions might be thrown and what they might mean.
  234.       if (ex.result != Cr.NS_ERROR_UNEXPECTED)
  235.         throw ex;
  236.     }
  237.   },
  238.  
  239.   /**
  240.    * Lock a pref so it can't be changed.
  241.    *
  242.    * @param   prefName  {String|Array}
  243.    *          the pref to lock, or an array of prefs to lock
  244.    */
  245.   lock: function(prefName) {
  246.     if (isArray(prefName))
  247.       prefName.map(this.lock, this);
  248.  
  249.     this._prefSvc.lockPref(prefName);
  250.   },
  251.  
  252.   /**
  253.    * Unlock a pref so it can be changed.
  254.    *
  255.    * @param   prefName  {String|Array}
  256.    *          the pref to lock, or an array of prefs to lock
  257.    */
  258.   unlock: function(prefName) {
  259.     if (isArray(prefName))
  260.       prefName.map(this.unlock, this);
  261.  
  262.     this._prefSvc.unlockPref(prefName);
  263.   },
  264.  
  265.   /**
  266.    * Whether or not the given pref is locked against changes.
  267.    *
  268.    * @param   prefName  {String|Array}
  269.    *          the pref to check, or an array of prefs to check
  270.    *
  271.    * @returns {Boolean|Array}
  272.    *          whether or not the pref has a user-set value; or, if the caller
  273.    *          provided an array of pref names, an array of booleans indicating
  274.    *          whether or not the prefs have user-set values
  275.    */
  276.   locked: function(prefName) {
  277.     if (isArray(prefName))
  278.       return prefName.map(this.locked, this);
  279.  
  280.     return this._prefSvc.prefIsLocked(prefName);
  281.   },
  282.  
  283.   /**
  284.    * Start observing a pref.
  285.    *
  286.    * The callback can be a function or any object that implements nsIObserver.
  287.    * When the callback is a function and thisObject is provided, it gets called
  288.    * as a method of thisObject.
  289.    *
  290.    * @param   prefName    {String}
  291.    *          the name of the pref to observe
  292.    *
  293.    * @param   callback    {Function|Object}
  294.    *          the code to notify when the pref changes;
  295.    *
  296.    * @param   thisObject  {Object}  [optional]
  297.    *          the object to use as |this| when calling a Function callback;
  298.    *
  299.    * @returns the wrapped observer
  300.    */
  301.   observe: function(prefName, callback, thisObject) {
  302.     let fullPrefName = this._prefBranch + (prefName || "");
  303.  
  304.     let observer = new PrefObserver(fullPrefName, callback, thisObject);
  305.     Preferences._prefSvc.addObserver(fullPrefName, observer, true);
  306.     observers.push(observer);
  307.  
  308.     return observer;
  309.   },
  310.  
  311.   /**
  312.    * Stop observing a pref.
  313.    *
  314.    * You must call this method with the same prefName, callback, and thisObject
  315.    * with which you originally registered the observer.  However, you don't have
  316.    * to call this method on the same exact instance of Preferences; you can call
  317.    * it on any instance.  For example, the following code first starts and then
  318.    * stops observing the "foo.bar.baz" preference:
  319.    *
  320.    *   let observer = function() {...};
  321.    *   Preferences.observe("foo.bar.baz", observer);
  322.    *   new Preferences("foo.bar.").ignore("baz", observer);
  323.    *
  324.    * @param   prefName    {String}
  325.    *          the name of the pref being observed
  326.    *
  327.    * @param   callback    {Function|Object}
  328.    *          the code being notified when the pref changes
  329.    *
  330.    * @param   thisObject  {Object}  [optional]
  331.    *          the object being used as |this| when calling a Function callback
  332.    */
  333.   ignore: function(prefName, callback, thisObject) {
  334.     let fullPrefName = this._prefBranch + (prefName || "");
  335.  
  336.     // This seems fairly inefficient, but I'm not sure how much better we can
  337.     // make it.  We could index by fullBranch, but we can't index by callback
  338.     // or thisObject, as far as I know, since the keys to JavaScript hashes
  339.     // (a.k.a. objects) can apparently only be primitive values.
  340.     let [observer] = observers.filter(function(v) v.prefName   == fullPrefName &&
  341.                                                   v.callback   == callback &&
  342.                                                   v.thisObject == thisObject);
  343.  
  344.     if (observer) {
  345.       Preferences._prefSvc.removeObserver(fullPrefName, observer);
  346.       observers.splice(observers.indexOf(observer), 1);
  347.     }
  348.   },
  349.  
  350.   resetBranch: function(prefBranch) {
  351.     try {
  352.       this._prefSvc.resetBranch(prefBranch);
  353.     }
  354.     catch(ex) {
  355.       // The current implementation of nsIPrefBranch in Mozilla
  356.       // doesn't implement resetBranch, so we do it ourselves.
  357.       if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED)
  358.         this.reset(this._prefSvc.getChildList(prefBranch, []));
  359.       else
  360.         throw ex;
  361.     }
  362.   },
  363.  
  364.   /**
  365.    * The branch of the preferences tree to which this instance provides access.
  366.    * @private
  367.    */
  368.   _prefBranch: "",
  369.  
  370.   /**
  371.    * Preferences Service
  372.    * @private
  373.    */
  374.   get _prefSvc() {
  375.     let prefSvc = Cc["@mozilla.org/preferences-service;1"].
  376.                   getService(Ci.nsIPrefService).
  377.                   getBranch(this._prefBranch).
  378.                   QueryInterface(Ci.nsIPrefBranch2);
  379.     this.__defineGetter__("_prefSvc", function() prefSvc);
  380.     return this._prefSvc;
  381.   }
  382.  
  383. };
  384.  
  385. // Give the constructor the same prototype as its instances, so users can access
  386. // preferences directly via the constructor without having to create an instance
  387. // first.
  388. Preferences.__proto__ = Preferences.prototype;
  389.  
  390. /**
  391.  * A cache of pref observers.
  392.  *
  393.  * We use this to remove observers when a caller calls Preferences::ignore.
  394.  *
  395.  * All Preferences instances share this object, because we want callers to be
  396.  * able to remove an observer using a different Preferences object than the one
  397.  * with which they added it.  That means we have to identify the observers
  398.  * in this object by their complete pref name, not just their name relative to
  399.  * the root branch of the Preferences object with which they were created.
  400.  */
  401. let observers = [];
  402.  
  403. function PrefObserver(prefName, callback, thisObject) {
  404.   this.prefName = prefName;
  405.   this.callback = callback;
  406.   this.thisObject = thisObject;
  407. }
  408.  
  409. PrefObserver.prototype = {
  410.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
  411.  
  412.   observe: function(subject, topic, data) {
  413.     // The pref service only observes whole branches, but we only observe
  414.     // individual preferences, so we check here that the pref that changed
  415.     // is the exact one we're observing (and not some sub-pref on the branch).
  416.     if (data != this.prefName)
  417.       return;
  418.  
  419.     if (typeof this.callback == "function") {
  420.       let prefValue = Preferences.get(this.prefName);
  421.  
  422.       if (this.thisObject)
  423.         this.callback.call(this.thisObject, prefValue);
  424.       else
  425.         this.callback(prefValue);
  426.     }
  427.     else // typeof this.callback == "object" (nsIObserver)
  428.       this.callback.observe(subject, topic, data);
  429.   }
  430. };
  431.  
  432. function isArray(val) {
  433.   // We can't check for |val.constructor == Array| here, since the value
  434.   // might be from a different context whose Array constructor is not the same
  435.   // as ours, so instead we match based on the name of the constructor.
  436.   return (typeof val != "undefined" && val != null && typeof val == "object" &&
  437.           val.constructor.name == "Array");
  438. }
  439.  
  440. function isObject(val) {
  441.   // We can't check for |val.constructor == Object| here, since the value
  442.   // might be from a different context whose Object constructor is not the same
  443.   // as ours, so instead we match based on the name of the constructor.
  444.   return (typeof val != "undefined" && val != null && typeof val == "object" &&
  445.           val.constructor.name == "Object");
  446. }
  447.